\chapter{运算符重载}\label{ch12}

在\hyperref[ch02]{第2章}的曼德勃罗集绘制器中，我们使用了\texttt{num} crate的\texttt{Complex}类型来表示一个复平面中的点：
\begin{minted}{Rust}
    #[derive(Clone, Copy, Debug)]
    struct Complex<T> {
        /// 复数的实部
        re: T,

        /// 复数的虚部
        im: T,
    }
\end{minted}

我们可以使用Rust的\texttt{+}和\texttt{*}运算符，像操作内建类型一样把\texttt{Complex}值相加和相乘：
\begin{minted}{Rust}
    z = z * z + c;
\end{minted}

你也可以让你自己的类型支持算数和其他运算符，只需要实现一些内建的trait。这被称为\emph{运算符重载(operator overloading)}，它的效果也类似于C++、C\#、Python和Ruby中的运算符重载。

如\hyperref[t12-1]{表12-1}所示，这些用于重载运算符的trait根据支持的语言部分被分为几个类别。在本章中，我们将介绍每一种类别。我们的目的不只是帮你把自己的类型漂亮地集成到语言中，还是为了让你更好地了解如何编写使用这些运算符的泛型函数，例如在\nameref{RevBound}中介绍的点积函数。本章还会深入了解语言某些功能本身是如何实现的。

\begin{table}[htbp]
    \centering
    \caption{运算符重载的trait汇总}
    \label{t12-1}
    \begin{tabular}{lll}
        \hline
        \textbf{类别}   & \textbf{trait}    & \textbf{运算符}   \\
        \hline

        \multirow{2}{*}{一元运算符} & \texttt{std::ops::Neg}    & \texttt{-x}   \\
        & \texttt{std::ops::Not} \cellcolor{tablecolor} & \texttt{!x} \cellcolor{tablecolor}  \\
        \hline

        \multirow{5}{*}{算术运算符} & \texttt{std::ops::Add}    & \texttt{x + y}\\
        & \texttt{std::ops::Sub} \cellcolor{tablecolor} & \texttt{x - y} \cellcolor{tablecolor} \\
        & \texttt{sdt::ops::Mul}    & \texttt{x * y}\\
        & \texttt{std::ops::Div} \cellcolor{tablecolor} & \texttt{x / y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::Rem}    & \texttt{x \% y}   \\
        \hline
        
        \multirow{5}{*}{位运算符}   & \texttt{std::ops::BitAnd} \cellcolor{tablecolor} & \texttt{x \& y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::BitOr}  & \texttt{x | y}    \\
        & \texttt{std::ops::BitXor} \cellcolor{tablecolor} & \texttt{x \^{} y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::Shl}    & \texttt{x << y}   \\
        & \texttt{std::ops::Shr}    \cellcolor{tablecolor} & \texttt{x >> y} \cellcolor{tablecolor} \\
        \hline

        \multirow{5}{*}{复合赋值算术运算符}  & \texttt{std::ops::AddAssign} & \texttt{x += y} \\
        & \texttt{std::ops::SubAssign} \cellcolor{tablecolor} & \texttt{x -= y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::MulAssign}  & \texttt{x *= y}  \\
        & \texttt{std::ops::DivAssign} \cellcolor{tablecolor} & \texttt{x /= y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::RemAssign}  & \texttt{x \%= y} \\
        \hline

        \multirow{5}{*}{复合赋值位运算符} & \texttt{std::ops::BitAndAssign} \cellcolor{tablecolor} & \texttt{x \&= y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::BitOrAssign}& \texttt{x |= y} \\
        & \texttt{std::ops::BitXorAssign} \cellcolor{tablecolor} & \texttt{x \^{}= y} \cellcolor{tablecolor} \\
        & \texttt{std::ops::ShlAssign}  & \texttt{x <<= y} \\
        & \texttt{std::ops::ShrAssign}    \cellcolor{tablecolor} & \texttt{x >>= y}   \cellcolor{tablecolor} \\
        \hline

        \multirow{2}{*}{比较}   & \texttt{std::cmp::PartialEq}  & \texttt{x == y, x != y}   \\
        & \texttt{std::cmp::PartialOrd} \cellcolor{tablecolor} & \texttt{x < y, x <= y, x > y, x >= y} \cellcolor{tablecolor} \\
        \hline

        \multirow{2}{*}{索引}   & \texttt{std::ops::Index}  & \texttt{x[y], \&x[y]} \\
        & \texttt{std::ops::IndexMut} \cellcolor{tablecolor} & \texttt{x[y] = z, \&mut x[y]} \cellcolor{tablecolor} \\
    \end{tabular}
\end{table}

\section{算术和位运算符}

在Rust中，表达式\texttt{a + b}实际上是\texttt{a.add(b)}的缩写，即对标准库中的\texttt{std::ops::Add} trait的\texttt{add}方法的调用。Rust的标准数值类型都实现了\texttt{std::ops::Add}。为了让表达式\texttt{a + b}能用于\texttt{Complex}类型的值，\texttt{num} crate为\texttt{Complex}类型实现了这个trait。其他运算符也有类似的trait：\texttt{a * b}是\texttt{a.mul(b)}的缩写，这个方法来自\texttt{std::ops::Mul} trait，\texttt{std::ops::Neg}包含取负数运算符，等等。

如果你想尝试写\texttt{z.add(c)}，你需要在作用域中引入\texttt{Add} trait，这样这个方法才可见。然后，你就可以把所有算术看作函数调用：\footnote{Lisp程序员狂喜！表达式\texttt{<i32 as Add>::add}是\texttt{i32}的\texttt{+}运算符，被捕获为函数类型的值。}

\begin{minted}{Rust}
    use std::ops::Add;

    assert_eq!(4.125f32.add(5.75), 9.875);
    assert_eq!(10.add(20), 10 + 20);
\end{minted}

这是\texttt{std::ops::Add}的定义：
\begin{minted}{Rust}
    trait Add<Rhs = Self> {
        type Output;
        fn add(self, rhs: Rhs) -> Self::Output;
    }
\end{minted}

换句话说，\texttt{Add<T>} trait让你的类型可以加上\texttt{T}类型的值。例如，为了让你的类型能加上\texttt{i32}和\texttt{u32}，你的类型必须实现了\texttt{Add<i32>}和\texttt{Add<u32>}。trait的类型参数\texttt{Rhs}默认是\texttt{Self}，因此如果你想实现两个相同类型的值的加法，可以直接实现\texttt{Add} trait。关联类型\texttt{Output}表示加法结果的类型。

例如，为了能把\texttt{Complex<i32>}值相加，\texttt{Complex<i32>}必须实现\texttt{Add<Complex<i32>>}。因为我们是把一个类型加到同类型的值上，所以可以简单地写\texttt{Add}：
\begin{minted}{Rust}
    use std::ops::Add;

    impl Add for Complex<i32> {
        type Output = Complex<i32>;
        fn add(self, rhs: Self) -> Self {
            Complex {
                re: self.re + rhs.re,
                im: self.im + rhs.im,
            }
        }
    }
\end{minted}

当然，我们不需要单独为\texttt{Complex<i32>}、\texttt{Complex<f32>}、\texttt{Complex<f64>}等实现\texttt{Add}。除了类型不同以外所有的定义看起来完全相同，所以我们可以写一个覆盖所有情况的泛型实现，只要实部和虚部的类型支持加法：
\begin{minted}{Rust}
    impl<T> Add for Complex<T>
    where
        T: Add<Output = T>,
    {
        type Output = Self;
        fn add(self, rhs: Self) -> Self {
            Complex {
                re: self.re + rhs.re,
                im: self.im + rhs.im,
            }
        }
    }
\end{minted}

通过\texttt{where T: Add<Output = T>}，我们可以把\texttt{T}限制为可以与同类型的值相加并且返回同类型的值的类型。这个限制是有原因的，但我们可以进一步放松限制：\texttt{Add} trait并不要求\texttt{+}两侧的操作数类型相同，也不需要返回相同的类型。因此一个最大限度的泛型实现可以让左边的操作数和右边的操作数类型不同，并让返回值中的实部和虚部的类型是加法返回的类型：
\begin{minted}{Rust}
    use std::ops::Add;
    impl<L, R> Add<Complex<R>> for Complex<L>
        where L: Add<R>
    {
        type Output = Complex<L::Output>;
        fn add(self, rhs: Complex<R>) -> Self::Output {
            Complex {
                re: self.re + rhs.re,
                im: self.im + rhs.im,
            }
        }
    }
\end{minted}

然而在实践中，Rust更倾向于避免支持混合类型的操作。我们的类型参数\texttt{L}必须要实现\texttt{Add<R>}，但并没有多少类型实现了与其他类型相加的trait，所以一般\texttt{L}和\texttt{R}都是相同的类型。因此到最后，这个极致泛型化的版本可能并不比之前更简单的泛型定义版本有用。

Rust中为算术和位运算符设计的内建trait被分为三组：一元运算符、二元运算符和复合赋值运算符。每个组中的所有trait和它们的方法的形式都相同，因此我们会从中挑选一个作为示例。

\subsection{一元运算符}\label{unop}
除了解引用运算符\texttt{*}将在\nameref{deref}一节中单独介绍之外，Rust还有可以自定义的一元运算符，如\hyperref[t12-2]{表12-2}所示。

\begin{table}[htbp]
    \centering
    \caption{内建的一元运算符的trait}
    \label{t12-2}
    \begin{tabular}{p{0.3\textwidth}p{0.3\textwidth}p{0.3\textwidth}}
        \hline
        \textbf{trait名称}  & \textbf{表达式}   & \textbf{等价的表达式} \\
        \hline
        \texttt{std::ops::Neg}  & \texttt{-x}   & \texttt{x.neg()}  \\
        \rowcolor{tablecolor}
        \texttt{std::ops::Not}  & \texttt{!x}   & \texttt{x.not()}  \\
    \end{tabular}
\end{table}

所有Rust的有符号数值类型都实现了\texttt{std::ops::Neg}，用于一元运算符\texttt{-}。整数类型和\texttt{bool}类型实现了\texttt{std::ops::Not}，用于一元运算符\texttt{!}。这些类型的引用也有相应的实现。

注意\texttt{!}会按位取反整数（即反转所有位）值、反转\texttt{bool}值。它同时提供C和C++中的\texttt{!}和\texttt{\~{}}的功能。

这些trait的定义很简单：
\begin{minted}{Rust}
    trait Neg {
        type Output;
        fn neg(self) -> Self::Output;
    }

    trait Not {
        type Output;
        fn not(self) -> Self::Output;
    }
\end{minted}

求一个复数值的负数只需要简单的求它的每个部分的负数。这里我们可以为\texttt{Complex}值写一个泛型的求负数实现：
\begin{minted}{Rust}
    use std::ops::Neg;

    impl<T> Neg for Complex<T>
    where
        T: Neg<Output = T>,
    {
        type Output = Complex<T>;
        fn neg(self) -> Complex<T> {
            Complex {
                re: -self.re,
                im: -self.im,
            }
        }
    }
\end{minted}

\subsection{二元运算符}\label{biop}
Rust的二元算术和位运算以及对应的内建trait如\hyperref[t12-3]{表12-3}所示。

\begin{table}[htbp]
    \centering
    \caption{二元运算符的内建trait}
    \label{t12-3}
    \begin{tabular}{llll}
        \hline
        \textbf{类别}   & \textbf{trait名称}    & \textbf{表达式}   & \textbf{等价的表达式} \\
        \hline

        \multirow{5}{*}{算术运算符} & \texttt{std::ops::Add}    & \texttt{x + y}    & \texttt{x.add(y)} \\
        & \texttt{std::ops::Sub} \cellcolor{tablecolor} & \texttt{x - y} \cellcolor{tablecolor} & \texttt{x.sub(y)} \cellcolor{tablecolor}  \\
        & \texttt{std::ops::Mul}    & \texttt{x * y}    & \texttt{x.mul(y)} \\
        & \texttt{std::ops::Div} \cellcolor{tablecolor} & \texttt{x / y} \cellcolor{tablecolor} & \texttt{x.div(y)} \cellcolor{tablecolor}  \\
        & \texttt{std::ops::Rem}    & \texttt{x \% y}   & \texttt{x.rem(y)} \\

        \hline

        \multirow{5}{*}{位运算符}   & \texttt{std::ops::BitAnd} \cellcolor{tablecolor}  & \texttt{x \& y} \cellcolor{tablecolor}    & \texttt{x.bitand(y)} \cellcolor{tablecolor}   \\
        & \texttt{std::ops::BitOr}  & \texttt{x | y}    & \texttt{x.bitor(y)}   \\
        & \texttt{std::ops::BitXor} \cellcolor{tablecolor} & \texttt{x \^{} y} \cellcolor{tablecolor}   & \texttt{x.bitxor(y)} \cellcolor{tablecolor}   \\
        & \texttt{std::ops::Shl}    & \texttt{x << y}   & \texttt{x.shl(y)} \\
        & \texttt{std::ops::Shr} \cellcolor{tablecolor} & \texttt{x >> y} \cellcolor{tablecolor}    & \texttt{x.shr(y)} \cellcolor{tablecolor}  \\
    \end{tabular}
\end{table}

Rust的所有数值类型都实现了算术运算符。Rust的整数类型和\texttt{bool}类型实现了位运算符。还有一个操作数是引用或者两个操作数都是引用的版本的实现。

所有这些trait都有相同的形式。例如\texttt{std::ops::BitXor}(用于\texttt{\^{}}运算符)的定义看起来像这样：
\begin{minted}{Rust}
    trait BitXor<Rhs = Self> {
        type Output;
        fn bitxor(self, rhs: Rhs) -> Self::Output;
    }
\end{minted}

在本章的开始处，我们还展示了这个分类中的另一个trait \texttt{std::ops::Add}，以及几个示例实现。

你可以使用\texttt{+}运算符来连接一个\texttt{String}和一个\texttt{\&str}切片或者另一个\texttt{String}。然而，Rust不允许\texttt{+}左侧的操作数是\texttt{\&str}，以避免通过在左侧反复连接小块来构建长字符串。（这样做的性能很差，需要最终字符串长度平方数量级的时间。）通常来说，\texttt{write!}宏是更好的通过多个小块构建字符串的方式，我们会在\nameref{AppendText}一节中展示如何做到这一点。

\subsection{复合赋值运算符}\label{assign}
复合赋值运算符例如\texttt{x += y}或\texttt{x \&= y}需要两个参数，然后对它们进行一些操作例如加法或位与，最后把结果保存到左侧的操作数。在Rust中，复合赋值表达式的值总是\texttt{()}，而不是最后被存储的值。

很多语言都有这些运算符，并通常把它们定义为像\texttt{x = x + y}或\texttt{x = x \& y}这样的表达式的缩写。然而，Rust并没有采用这种方案。作为代替，\texttt{x += y}是方法调用\texttt{x.add\_assign(y)}的缩写，而\texttt{add\_assign}是\texttt{std::ops::AddAssign} trait唯一的方法：
\begin{minted}{Rust}
    trait AddAssign<Rhs = Self> {
        fn add_assign(&mut self, rhs: Rhs);
    }
\end{minted}

\hyperref[t12-4]{表12-4}展示了Rust的所有复合赋值运算符以及实现它们的所有内建trait。

\begin{table}[htbp]
    \centering
    \caption{复合赋值运算符对应的内建trait}
    \label{t12-4}
    \begin{tabular}{llll}
        \hline
        \textbf{类别}   & \textbf{trait名称}    & \textbf{表达式}   & \textbf{等价的表达式} \\
        \hline

        \multirow{5}{*}{算术运算符} & \texttt{std::ops::AddAssign}    & \texttt{x += y}    & \texttt{x.add\_assign(y)} \\
        & \texttt{std::ops::SubAssign} \cellcolor{tablecolor} & \texttt{x -= y} \cellcolor{tablecolor} & \texttt{x.sub\_assign(y)} \cellcolor{tablecolor}  \\
        & \texttt{std::ops::MulAssign}    & \texttt{x *= y}    & \texttt{x.mul\_assign(y)} \\
        & \texttt{std::ops::DivAssign} \cellcolor{tablecolor} & \texttt{x /= y} \cellcolor{tablecolor} & \texttt{x.div\_assign(y)} \cellcolor{tablecolor}  \\
        & \texttt{std::ops::RemAssign}    & \texttt{x \%= y}   & \texttt{x.rem\_assign(y)} \\

        \hline

        \multirow{5}{*}{位运算符}   & \texttt{std::ops::BitAndAssign} \cellcolor{tablecolor}  & \texttt{x \&= y} \cellcolor{tablecolor}    & \texttt{x.bitand\_assign(y)} \cellcolor{tablecolor}   \\
        & \texttt{std::ops::BitOrAssign}  & \texttt{x |= y}    & \texttt{x.bitor\_assign(y)}   \\
        & \texttt{std::ops::BitXorAssign} \cellcolor{tablecolor} & \texttt{x \^{}= y} \cellcolor{tablecolor}   & \texttt{x.bitxor\_assign(y)} \cellcolor{tablecolor}   \\
        & \texttt{std::ops::ShlAssign}    & \texttt{x <<= y}   & \texttt{x.shl\_assign(y)} \\
        & \texttt{std::ops::ShrAssign} \cellcolor{tablecolor} & \texttt{x >>= y} \cellcolor{tablecolor}    & \texttt{x.shr\_assign(y)} \cellcolor{tablecolor}  \\
    \end{tabular}
\end{table}

Rust的所有数值类型都实现了算术复合赋值运算符。Rust的整数类型和\texttt{bool}实现了位运算复合赋值运算符。一个为我们的\texttt{Complex}类型的\texttt{AddAssign}的泛型实现非常直观：
\begin{minted}{Rust}
    use std::ops::AddAssign;

    impl<T> AddAssign for Complex<T>
    where
        T: AddAssign<T>
    {
        fn add_assign(&mut self, rhs: Complex<T>) {
            self.re += rhs.re;
            self.im += rhs.im;
        }
    }
\end{minted}

复合赋值运算符的内建trait和相应的二元运算符的内建trait完全独立。实现\\
\texttt{std::ops::Add}并不会自动实现\texttt{std::ops::AddAssign}。如果你想让Rust允许你的类型使用\texttt{+=}运算符，那你必须自己实现\texttt{AddAssign}。

\section{相等性比较}\label{equal}

Rust的相等运算符\texttt{==}和\texttt{!=}，是\texttt{std::cmp::PartialEq} trait的\texttt{eq}和\texttt{ne}方法的缩写：
\begin{minted}{Rust}
    assert_eq!(x == y, x.eq(&y));
    assert_eq!(x != y, x.ne(&y));
\end{minted}

这是\texttt{std::cmp::PartialEq}的定义：
\begin{minted}{Rust}
    trait PartialEq<Rhs = Self>
    where
        Rhs: ?Sized,
    {
        fn eq(&self, other: &Rhs) -> bool;
        fn ne(&self, other: &Rhs) -> bool {
            !self.eq(other)
        }
    }
\end{minted}

因为\texttt{ne}方法有默认的定义，所以实现\texttt{PartialEq}时只需要实现\texttt{eq}。这里有一个\texttt{Complex}类型的实现：
\begin{minted}{Rust}
    impl<T: PartialEq> PartialEq for Complex<T> {
        fn eq(&self, other: &Complex<T>) -> bool {
            self.re == other.re && self.im == other.im
        }
    }
\end{minted}

换句话说，就是为任何可以比较相等性的类型\texttt{T}实现\texttt{Complex<T>}的比较运算。假设我们已经为\texttt{Complex}实现了\texttt{std::ops::Mul}，那我们现在可以写：
\begin{minted}{Rust}
    let x = Complex { re: 5, im: 2 };
    let y = Complex { re: 2, im: 5 };
    assert_eq!(x * y, Complex { re: 0, im: 29 });
\end{minted}

\texttt{PartialEq}的实现通常都是这里展示的形式：比较左右操作数的每一个字段是否都相同。这写起来非常无聊，而且相等性比较是通常都需要支持的操作，所以如果你要求的话，Rust可以为你自动生成一个\texttt{PartialEq}的实现。简单地像这样给这个类型的定义的\texttt{derive}属性加上\texttt{PartialEq}：
\begin{minted}{Rust}
    #[derive(Clone, Copy, Debug, PartialEq)]
    struct Complex<T> {
        ...
    }
\end{minted}

Rust自动生成的实现跟我们手写的代码基本完全相同，就是依次比较每一个字段或元素。Rust也可以为\texttt{enum}类型自动生成\texttt{PartialEq}实现。当然，这些类型持有的每个值自身必须实现了\texttt{PartialEq}。

与算术和位trait以值传递操作数不同，\texttt{PartialEq}以引用获取操作数。这意味着比较像\texttt{String}、\texttt{Vec}、\texttt{HashMap}这样的非\texttt{Copy}值不会导致它们被移动，否则可能会导致问题：
\begin{minted}{Rust}
    let s = "d\x6fv\x65t\x61i\x6c".to_string();
    let t = "\x64o\x76e\x74a\x69l".to_string();

    assert!(s == t); // s和t只会被借用

    // 因此它们仍然保持原来的值
    assert_eq!(format!("{} {}", s, t), "dovetail dovetail");
\end{minted}

让我们看看这个trait中的\texttt{Rhs}类型参数的约束，它的类型我们之前从来没有见过：
\begin{minted}{Rust}
    where
        Rhs: ?Sized,
\end{minted}

这放松了Rust通常要求类型参数必须是固定大小类型的要求，所以我们才可以写\texttt{PartialEq<str>}或\texttt{PartialEq<[T]>}这样的trait。\texttt{eq}和\texttt{ne}方法以类型\texttt{\&Rhs}获取参数，并用\texttt{\&str}或者\texttt{\&[T]}来进行比较是完全合理的。因为\texttt{str}实现了\texttt{PartialEq<str>}，所以下面的断言是等价的：
\begin{minted}{Rust}
    assert!("ungula" != "ungulate");
    assert!("ungula".ne("ungulate"));
\end{minted}

这里，\texttt{Self}和\texttt{Rhs}都是无固定大小的\texttt{str}类型，这导致\texttt{ne}的\texttt{self}和\texttt{rhs}参数都只能是\texttt{\&str}\\
值。我们将在\nameref{sized}中讨论固定大小类型、非固定大小类型以及\texttt{Sized} trait。

为什么这个trait被称为\texttt{PartialEq}？相等性是\emph{等价关系(equivalence relation)}的传统数学定义中的一种，等价关系需要满足三个要求。对于任意值\texttt{x}和\texttt{y}：
\begin{itemize}
    \item 如果\texttt{x == y}为真，那么\texttt{y == x}也必须为真。换句话说，交换等价性比较的两侧并不会影响结果。
    \item 如果\texttt{x == y}和\texttt{y == z}，那么\texttt{x == z}也必须为真。给定一个值链，如果其中相邻的两个值相等，那么链中的每个值一定等于其他每一个值。相等性有传递性。
    \item \texttt{x == x}必须总是为真。
\end{itemize}

最后一个要求看起来似乎太明显以至于不值得列出来，但这正是导致事情变得复杂的地方。Rust的\texttt{f32}和\texttt{f64}是IEEE标准的浮点数类型。根据这个标准，像\texttt{0.0/0.0}以及其他没有合适的结果的表达式必须产生一个特殊的\emph{非数(not-a-number)}值，通常被称为NaN值。标准还要求一个NaN值必须和其他任何值都不相等——包括它自己。例如，这个标准要求以下行为：
\begin{minted}{Rust}
    assert!(f64::is_nan(0.0 / 0.0));
    assert_eq!(0.0 / 0.0 == 0.0 / 0.0, false);
    assert_eq!(0.0 / 0.0 != 0.0 / 0.0, true);
\end{minted}

另外，任何与NaN的顺序性比较都必须返回假：
\begin{minted}{Rust}
    assert_eq!(0.0 / 0.0 < 0.0 / 0.0, false);
    assert_eq!(0.0 / 0.0 > 0.0 / 0.0, false);
    assert_eq!(0.0 / 0.0 <= 0.0 / 0.0, false);
    assert_eq!(0.0 / 0.0 >= 0.0 / 0.0, false);
\end{minted}

因此尽管Rust的\texttt{==}运算符满足前两个等价关系的要求，但使用IEEE浮点数值显然不满足第三个要求。这被称为\emph{部分等价关系(partial equivalence relation)}，因此Rust使用名称\texttt{PartialEq}作为内建的\texttt{==}运算符。如果你写泛型代码时类型参数已知是\texttt{PartialEq}，那你应该假设它们满足前两个要求，但你不应该假设那些值总是和它们自身相等。

这有点违反直觉，如果你不保持警惕可能会导致bug。如果你希望你的泛型代码满足完全的等价关系，你可以使用\texttt{std::cmp::Eq} trait作为约束，它代表完全的等价关系：如果一个类型实现了\texttt{Eq}，那么对于任何该类型的值\texttt{x}，\texttt{x == x}一定为\texttt{true}。在实践中，几乎所有实现了\texttt{PartialEq}的类型也实现了\texttt{Eq}，\texttt{f32}和\texttt{f64}是标准库中仅有的实现了\texttt{PartialEq}但却没有实现\texttt{Eq}的类型。

标准库将\texttt{Eq}定义为\texttt{PartialEq}的扩展，没有添加任何新方法：
\begin{minted}{Rust}
    trait Eq: PartialEq<Self> {}
\end{minted}

如果你的类型是\texttt{PartialEq}并且你希望它也是\texttt{Eq}，那你必须显式地实现\texttt{Eq}，即使你并不需要为此再定义任何新的方法或类型。因此为我们的\texttt{Complex}类型实现\texttt{Eq}非常迅速：
\begin{minted}{Rust}
    impl<T: Eq> Eq for Complex<T> {}
\end{minted}

我们也可以直接在\texttt{Complex}类型定义中的\texttt{derive}属性里加上\texttt{Eq}：
\begin{minted}{Rust}
    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    struct Complex<T> {
        ...
    }
\end{minted}

在泛型类型上派生实现会依赖类型参数。有了这个\texttt{derive}属性，\texttt{Complex<i32>}将会实现\texttt{Eq}，因为\texttt{i32}实现了\texttt{Eq}；但\texttt{Complex<f32>}将只实现\texttt{PartialEq}，因为\texttt{f32}并没有实现\texttt{Eq}。

当你自己实现\texttt{std::cmp::PartialEq}时，Rust无法检查你的\texttt{eq}和\texttt{ne}方法的定义是否真的满足等价关系的部分或全部要求。它们的行为可以是任意的。Rust简单地认为你实现等价性的方式满足用户的需求。

尽管\texttt{PartialEq}的定义为\texttt{ne}提供了默认的实现，如果你愿意的话也可以提供自己的实现。然而，你必须要保证\texttt{ne}和\texttt{eq}彼此完全互补。\texttt{PartialEq} trait的用户也会这么假设。

\section{顺序性比较}\label{cmp}

Rust用单个trait \texttt{std::cmp::PartialOrd}来指定比较运算符\texttt{<, >, <=, >=}的行为：
\begin{minted}{Rust}
    trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
    where
        Rhs: ?Sized,
    {
        fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;

        fn lt(&self, other: &Rhs) -> bool { ... }
        fn le(&self, other: &Rhs) -> bool { ... }
        fn gt(&self, other: &Rhs) -> bool { ... }
        fn ge(&self, other: &Rhs) -> bool { ... }
    }
\end{minted}

注意\texttt{PartialOrd<Rhs>}扩展了\texttt{PartialEq<Rhs>}：你只能对可以比较相等性的类型比较顺序性。

你唯一需要实现的\texttt{PartialOrd}的方法就是\texttt{partial\_cmp}。当\texttt{partial\_cmp}返回\texttt{Some(o)}时，\texttt{o}表示\texttt{self}和\texttt{other}的关系：
\begin{minted}{Rust}
    enum Ordering {
        Less,       // self < other
        Equal,      // self == other
        Greater,    // self > other
    }
\end{minted}

但如果\texttt{partial\_cmp}返回\texttt{None}，那么意味着\texttt{self}和\texttt{other}无法比较顺序：每一个都不比另一个大，也不相等。在所有的Rust基本类型中，只有浮点数的比较可能会返回\texttt{None}：确切地说，在比较\texttt{NaN}(not-a-number)和其他任何值时会返回\texttt{None}。我们已经在\nameref{equal}中给出了一些关于NaN值的背景。

和其他二元运算符一样，为了比较两个类型\texttt{Left}和\texttt{Right}，\texttt{Left}必须实现了\\
\texttt{PartialOrd<Right>}。像\texttt{x < y}或\texttt{x >= y}这样的表达式是对\texttt{PartialOrd}方法调用的缩写，如\hyperref[t12-5]{表12-5}所示。

\begin{table}[htbp]
    \centering
    \caption{顺序性比较运算符和\texttt{PartialOrd}的方法}
    \label{t12-5}
    \begin{tabular}{lll}
        \hline
        \textbf{表达式} & \textbf{等价的方法调用}   & \textbf{默认实现} \\
        \hline

        \texttt{x < y}  & \texttt{x.lt(y)}  & \texttt{x.partial\_cmp(\&y) == Some(Less)}    \\
        \rowcolor{tablecolor}
        \texttt{x > y}  & \texttt{x.gt(y)}  & \texttt{x.partial\_cmp(\&y) == Some(Greater)} \\
        \texttt{x <= y} & \texttt{x.le(y)}  & \texttt{matches!(x.partial\_cmp(\&y), Some(Less) | Some(Equal))}  \\
        \rowcolor{tablecolor}
        \texttt{x >= y} & \texttt{x.ge(y)}  & \texttt{matches!(x.partial\_cmp(\&y), Some(Greater) | Some(Equal))}   \\
    \end{tabular}
\end{table}

和之前的例子一样，这里展示的顺序性方法调用代码假设\texttt{std::cmp::PartialOrd}和\\
\texttt{std::cmp::Ordering}在作用域里。

如果你知道某个类型的两个值总是可以互相比较顺序性，那么你可以实现更加严格的\texttt{std::cmp::Ord} trait：
\begin{minted}{Rust}
    trait Ord: Eq + PartialOrd<Self> {
        fn cmp(&self, other: &Self) -> Ordering;
    }
\end{minted}

\texttt{cmp}方法直接返回\texttt{Ordering}，而不是像\texttt{partial\_cmp}一样返回\texttt{Option<Ordering>}：\texttt{cmp}总是返回两个参数相等或它们的相对顺序。几乎所有实现了\texttt{PartialOrd}的类型都应该实现\texttt{Ord}。在标准库中，\texttt{f32}和\texttt{f64}是仅有的例外。

因为复数没有自然的顺序性，我们不能使用上一节的\texttt{Complex}类型来展示\texttt{PartialOrd}的示例实现。作为替代，假设你正在使用一个类型，这个类型表示表示一个半开区间内的数字的集合：
\begin{minted}{Rust}
    #[derive(Debug, PartialEq)]
    struct Interval<T> {
        lower: T,   // 包括
        upper: T,   // 不包括
    }
\end{minted}

你可能会希望让这种类型是部分有序的：如果一个区间A完全落在另一个区间B之前，那么A小于B；如果两个不同的区间交叉，那么它们是无序的：每个区间里都有部分元素小于另一个区间里的部分元素；如果两个区间相同则相等。下面的\texttt{PartialOrd}的实现实现了这些规则：
\begin{minted}{Rust}
    use std::cmp::{Ordering, PartialOrd};

    impl<T: PartialOrd> PartialOrd<Interval<T>> for Interval<T> {
        fn partial_cmp(&self, other: &Interval<T>) -> Option<Ordering> {
            if self == other {
                Some(Ordering::Equal)
            } else if self.lower >= other.upper {
                Some(Ordering::Greater)
            } else if self.upper <= other.lower {
                Some(Ordering::Less)
            } else {
                None
            }
        }
    }
\end{minted}

有了这个实现，你可以写下面的代码：
\begin{minted}{Rust}
    assert!(Interval { lower: 10, upper: 20 } < Interval { lower: 20, upper: 40});
    assert!(Interval { lower: 7, upper: 8 } >= Interval { lower: 0, upper: 1 });
    assert!(Interval { lower: 7, upper: 8 } <= Interval { lower: 7, upper: 8 });

    // 交叉的区间彼此无序
    let left  = Interval { lower: 10, upper: 30 };
    let right = Interval { lower: 20, upper: 40 };
    assert!(!(left < right));
    assert!(!(left >= right));
\end{minted}

尽管你通常见到的是\texttt{PartialOrd}，但\texttt{Ord}定义的全序关系在有些场景下也是必要的，例如标准库中实现的排序算法。例如，只有\texttt{PartialOrd}实现的情况下不能对区间排序。如果你确实想排序它们，你需要消除无序的情况。例如，你可以根据上界排序，使用\texttt{sort\_by\_key}可以很容易做到这一点：
\begin{minted}{Rust}
    intervals.sort_by_key(|i| i.upper);
\end{minted}

实现\texttt{Ord}的类型还可以使用\texttt{Reverse}包装类型来反转顺序。对于任何实现了\texttt{Ord}的类型\texttt{T}，\\
\texttt{std::cmp::Reverse<T>}也会自动实现\texttt{Ord}，但是是以相反的顺序。例如，将我们的区间按照下界降序排列会很简单：
\begin{minted}{Rust}
    use std::cmp::Reverse;
    intervals.sort_by_key(|i| Reverse(i.lower));
\end{minted}

\section{\texttt{Index}与\texttt{IndexMut}}\label{index}

你可以通过为你的类型实现\texttt{std::ops::Index}和\texttt{std::ops::IndexMut} trait来指明索引表达式例如\texttt{a[i]}的行为。数组直接支持\texttt{[]}运算符，但对于任何其他类型，表达式\texttt{a[i]}通常是\texttt{*a.index(i)}的缩写，其中\texttt{index}是\texttt{std::ops::Index} trait的一个方法。然而，如果表达式被赋值或者可变借用，那么将是\texttt{*a.index\_mut(i)}的缩写，它是\texttt{std::ops::IndexMut} trait的一个方法。

这是这两个trait的定义：
\begin{minted}{Rust}
    trait Index<Idx> {
        type Output: ?Sized;
        fn index(&self, index: Idx) -> &Self::Output;
    }

    trait IndexMut<Idx>: Index<Idx> {
        fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
    }
\end{minted}

注意这些trait将索引的类型作为参数。你可以用单个\texttt{usize}值索引一个切片，来得到单个元素的引用，因为切片实现了\texttt{Index<usize>}。但你可以通过像\texttt{a[i..j]}这样的表达式来引用一个子切片，因为它们也实现了\texttt{Index<Range<usize>>}。这个表达式是如下表达式的缩写：
\begin{minted}{Rust}
    *a.index(std::ops::Range { start: i, end: j })
\end{minted}

Rust的\texttt{HashMap}和\texttt{BTreeMap}集合让你可以用任何可哈希或可比较的类型作为索引。下面的代码能正常工作，因为\texttt{HashMap<\&str, i32>}实现了\texttt{Index<\&str>}：
\begin{minted}{Rust}
    use std::collections::HashMap;
    let mut m = HashMap::new();
    m.insert("十", 10);
    m.insert("百", 100);
    m.insert("千", 1000);
    m.insert("万", 1_0000);
    m.insert("億", 1_0000_0000);

    assert_eq!(m["十"], 10);
    assert_eq!(m["千"], 1000);
\end{minted}

那两个索引表达式等价于：
\begin{minted}{Rust}
    use std::ops::Index;
    assert_eq!(*m.index("十"), 10);
    assert_eq!(*m.index("千"), 1000);
\end{minted}

\texttt{Index} trait的关联类型\texttt{Output}指定了索引表达式产生的值的类型：对于这里的\texttt{HashMap}，\texttt{Index}实现的\texttt{Output}类型是\texttt{i32}。

\texttt{IndexMut} trait扩展了\texttt{Index}，增加了一个\texttt{index\_mut}方法，这个方法获取\texttt{self}的引用，并返回一个指向\texttt{Output}值的可变引用。当索引表达式出现在一个必须可变的上下文中时，Rust会自动选择\texttt{index\_mut}。例如，假设我们写了下面的代码：
\begin{minted}{Rust}
    let mut desserts =
        vec!["Howalon".to_string(), "Soan papdi".to_string()];
    desserts[0].push_str(" (fictional)");
    desserts[1].push_str(" (real)");
\end{minted}

因为\texttt{push\_str}方法在\texttt{\&mut self}上进行操作，最后的两行等价于：
\begin{minted}{Rust}
    use std::ops::IndexMut;
    (*desserts.index_mut(0)).push_str(" (fictional)");
    (*desserts.index_mut(1)).push_str(" (real)");
\end{minted}

\texttt{IndexMut}的一个限制是，设计上它必须返回一个值的可变引用，所以你不能用类似\texttt{m["十"] = 10;}这样的表达式在\texttt{HashMap m}中插入一个值：哈希表需要首先为\texttt{"十"}创建一个值为默认的条目，然后返回指向它的可变引用。但并不是所有的类型都有开销很低的默认值，还有一些类型drop时可能开销很大。如果这种赋值先创建一个临时值然后立刻drop它将是一种浪费。（有计划在后续的语言版本中改善这种情况。）

索引最常见的用途是集合。例如，假设我们正在处理类似于我们在\hyperref[ch02]{第2章}中创建的曼德勃罗集绘制器的位图。回想一下我们的程序中包含这样的代码：
\begin{minted}{Rust}
    pixels[row * bounds.0 + column] = ...;
\end{minted}

如果用\texttt{Image<u8>}类型来充当二维数组显然会更好，这样我们就可以在不需要写出算术运算的情况下访问像素：
\begin{minted}{Rust}
    image[row][column] = ...;
\end{minted}

为了做到这一点，我们需要声明一个结构体：
\begin{minted}{Rust}
    struct Image<P> {
        width: usize,
        pixels: Vec<P>,
    }

    impl<P: Default + Copy> Image<P> {
        /// 创建一个给定大小的新图片。
        fn new(width: usize, height: usize) -> Image<P> {
            Image {
                width,
                pixels: vec![P::default(); width * height],
            }
        }
    }
\end{minted}

\texttt{Index}和\texttt{IndexMut}的实现将是：
\begin{minted}{Rust}
    impl<P> std::ops::Index<usize> for Image<P> {
        type Output = [P];
        fn index(&self, row: usize) -> &[P] {
            let start = row * self.width;
            &self.pixels[start..start + self.width]
        }
    }

    impl<P> std::ops::IndexMut<usize> for Image<P> {
        fn index_mut(&mut self, row: usize) -> &mut [P] {
            let start = row * self.width;
            &mut self.pixels[start..start + self.width]
        }
    }
\end{minted}

当你索引一个\texttt{Image}时，你会得到一个像素的切片，再索引切片你会得到单独的像素。

注意当我们写\texttt{image[row][column]}时，如果\texttt{row}越界了，我们的\texttt{.index()}方法会尝试索引越界的\texttt{self.pixels}，触发一个panic。这是\texttt{Index}和\texttt{IndexMut}的实现应有的行为：越界访问将被检测到并触发一个panic，当你索引一个越界的数组、切片或vector时也是这样。

\section{其他运算符}

Rust中并不是所有运算符都可以被重载。在Rust 1.50中，错误检查的\texttt{?}运算符只能用于\texttt{Result}和\texttt{Option}值，尽管有工作正在将其扩展到用户自定义的类型。 类似的，逻辑运算符\texttt{\&\&}和\texttt{||}只能用于布尔值。\texttt{..}和\texttt{..=}运算符总是创建一个表示范围的结构体、\texttt{\&}运算符总是借用引用、\texttt{=}运算符总是移动或者拷贝值。它们都不能被重载。

解引用运算符\texttt{*val}以及访问字段和方法的点运算符\texttt{val.field}和\texttt{val.method()}可以使用\nameref{deref} trait来重载，我们将在下一节介绍它。（我们没有在这里介绍是因为这些trait不仅仅只是重载一些运算符。）

Rust不支持重载函数调用运算符\texttt{f(x)}。作为代替，当你需要一个可调用的值时，你通常只需要写一个闭包。我们将在\hyperref[ch14]{第14章}中解释这是怎么工作的，并介绍特殊的\texttt{Fn}、\texttt{FnMut}和\texttt{FnOnce} trait。
